<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Api_channel_fields extends Api {

	var $custom_fields		= array();
	var $field_types		= array();
	var $ft_paths			= array();
	var $settings			= array();

	var $ee_base_ft			= FALSE;
	var $global_settings;
	
	// --------------------------------------------------------------------
	
	/**
	 * Set settings
	 *
	 * @access	public
	 */
	function set_settings($field_id, $settings)
	{
		if ( ! array_key_exists('field_name', $settings))
		{
			$settings['field_name'] = $field_id;
		}
		
		if ( ! array_key_exists($settings['field_type'], $this->field_types))
		{
			$this->field_types[$settings['field_type']] = $this->include_handler($settings['field_type']);
		}

		$this->settings[$field_id] = $settings;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Get settings
	 *
	 * @access	public
	 */
	function get_settings($field_id)
	{
		return isset($this->settings[$field_id]) ? $this->settings[$field_id] : array();
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Get global settings
	 *
	 * @access	public
	 */
	function get_global_settings($field_type)
	{
		if ( ! $this->global_settings)
		{
			$this->global_settings = array();
			$this->fetch_installed_fieldtypes();
		}
		
		if (isset($this->global_settings[$field_type]))
		{
			return $this->global_settings[$field_type];
		}
		
		return array();
	}
	
	// --------------------------------------------------------------------

	/**
	 * Fetch all fieldtypes
	 *
	 * @access	public
	 */
	function fetch_all_fieldtypes()
	{
		return $this->_fetch_fts('get_files');
	}
	
	// --------------------------------------------------------------------

	/**
	 * Fetch defined custom fields
	 *
	 * @access	public
	 */
	function fetch_installed_fieldtypes()
	{
		return $this->_fetch_fts('get_installed');
	}
	
	// --------------------------------------------------------------------

	/**
	 * Fetch fieldtypes
	 *
	 * Convenience method to reduce code duplication
	 *
	 * @access	private
	 */
	function _fetch_fts($method)
	{
		$this->EE->load->library('addons');
		$fts = $this->EE->addons->$method('fieldtypes');
		
		foreach($fts as $key => $data)
		{
			$this->field_types[$key] = $this->include_handler($key);

			if (isset($data['settings']))
			{
				$this->global_settings[$key] = unserialize(base64_decode($data['settings']));
			}
			
			$opts = get_class_vars($data['class']);
			$fts[$key] = array_merge($fts[$key], $opts['info']);
		}
		
		return $fts;
	}

	// --------------------------------------------------------------------

	/**
	 * Fetch defined custom fields
	 *
	 * @access	public
	 */
	function fetch_custom_channel_fields()
	{
		$this->EE->db->select('field_id, field_type, field_fmt, field_name, site_id, field_settings');
		$query = $this->EE->db->get('channel_fields');
		
		$cfields = array();
		$dfields = array();
		$rfields = array();
		$pfields = array();
		
		foreach ($query->result_array() as $row)
		{
			if ( ! array_key_exists($row['field_type'], $this->field_types))
			{
				$this->field_types[$row['field_type']] = $this->include_handler($row['field_type']);
			}

			$this->custom_fields[$row['field_id']] = $row['field_type'];
			
			if ($row['field_type'] == 'date')
			{
				$dfields[$row['site_id']][$row['field_name']] = $row['field_id'];
			}
			elseif ($row['field_type'] == 'rel')
			{
				$rfields[$row['site_id']][$row['field_name']] = $row['field_id'];
			}
			else
			{
				$field_handler = $this->field_types[$row['field_type']];
				$field_handler = is_object($field_handler) ? get_class($field_handler) : $field_handler;
				
				// Yay for PHP 4
				$class_vars = get_class_vars($field_handler);

				if (isset($class_vars['has_array_data']) && $class_vars['has_array_data'] === TRUE)
				{
					$pfields[$row['site_id']][$row['field_id']] = $row['field_type'];
				}
			}
			
			if (isset($row['field_settings']) && $row['field_settings'] != '')
			{
				$settings = unserialize(base64_decode($row['field_settings']));
				$settings['field_type'] = $row['field_type'];
				$settings['field_fmt'] = $row['field_fmt'];
				
				$this->set_settings($row['field_id'], $settings);
			}
			
			
			$cfields[$row['site_id']][$row['field_name']] = $row['field_id'];
		}

		return array(
			'custom_channel_fields'	=> $cfields,
			'date_fields'			=> $dfields,
			'relationship_fields'	=> $rfields,
			'pair_custom_fields'	=> $pfields
		);
	}

	// --------------------------------------------------------------------

	/**
	 * Include a custom field handler
	 *
	 * @access	public
	 */
	function include_handler($field_type)
	{
		if ( ! $this->ee_base_ft)
		{
			require_once APPPATH.'fieldtypes/EE_Fieldtype.php';
			$this->ee_base_ft = TRUE;
		}

		if ( ! isset($this->field_types[$field_type]))
		{
			$file = 'ft.'.$field_type.'.php';
			$path = PATH_FT.$field_type.'/';

			if ( ! file_exists($path.$file))
			{
				$path = PATH_THIRD.$field_type.'/';
				
				if ( ! file_exists($path.$file))
				{
					$path = PATH_MOD.$field_type.'/';

					if ( ! file_exists($path.$file))
					{
						show_error(sprintf($this->EE->lang->line('unable_to_load_field_type'),
						 						strtolower($file)));
					}
				}
			}
			
			require_once $path.$file;
			
			$this->ft_paths[$field_type] = $path;
			$this->field_types[$field_type] = ucfirst($field_type.'_ft');
		}
		
		return $this->field_types[$field_type];
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Setup or re-initialize field type handler
	 *
	 * @access	public
	 */
	function setup_handler($field_type, $return_obj = FALSE)
	{
		$field_id = FALSE;
		$frontend = FALSE;
		
		// Quite frequently all you have convenient access to
		// is a field_id. We can do a lookup based on some of the
		// other data we have.
		if (isset($this->custom_fields[$field_type]))
		{
			$frontend = TRUE;
			$field_id = $field_type;
			$field_type = $this->custom_fields[$field_type];
		}
		elseif (isset($this->settings[$field_type]))
		{
			$field_id = $field_type;
			$field_type = $this->settings[$field_id]['field_type'];
		}
		
		// Now that we know that we're definitely working
		// with a field_type name. Look for it.
		if ( ! isset($this->field_types[$field_type]))
		{
			return FALSE;
		}
		
		// Instantiate it if we haven't used it yet.
		if ( ! is_object($this->field_types[$field_type]))
		{
			$this->include_handler($field_type);

			$this->field_types[$field_type] =& $this->_instantiate_handler($field_type);
		}

		// If we started with a field_id, but we're not on the frontend
		// (which means fetch_custom_channel_fields didn't get called),
		// we need to make sure we have the proper field name.
		
		$field_name = FALSE;
		
		$settings = $this->get_settings($field_id);
		
		if (isset($settings['field_name']))
		{
			$field_name	= $settings['field_name'];
		}
		
		// Merge field settings with the global settings
		$settings = array_merge($this->get_global_settings($field_type), $settings);
		
		// Initialize fieldtype with settings for this field
		$this->field_types[$field_type]->_init(array(
			'settings'		=> $settings,
			'field_id'		=> $field_id,
			'field_name'	=> $field_name
		));
		
		// Remember what we set up so that apply
		// calls go to the right spot
		$this->field_type = $field_type;
		
		return ($return_obj) ? $this->field_types[$field_type] : TRUE;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Instantiate a fieldtype handler
	 *
	 * Normally this method does not need to be called, it's here for
	 * convenience. Use setup_handler() unless you're 100% sure you
	 * need this.
	 *
	 * @access	private
	 */
	function &_instantiate_handler($field_type)
	{
		$class		= $this->field_types[$field_type];
		$_ft_path	= $this->ft_paths[$field_type];
		
		$this->EE->load->add_package_path($_ft_path, FALSE);
		
		$obj = new $class();
		
		$this->EE->load->remove_package_path($_ft_path);
		
		return $obj;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Route the call to the proper handler
	 *
	 * Doing it this way so we don't have to pass objects around with PHP 4
	 * being annoying as it is.
	 *
	 * Using the name of the identical javascript function, and yes, I like it.
	 *
	 * @access	public
	 */
	function apply($method, $parameters = array())
	{
		$_ft_path = $this->ft_paths[$this->field_type];
		
		$this->EE->load->add_package_path($_ft_path, FALSE);
		
		$res = call_user_func_array(array(&$this->field_types[$this->field_type], $method), $parameters);

		$this->EE->load->remove_package_path($_ft_path);
		
		return $res;
	}

	// --------------------------------------------------------------------
	
	/**
	 * Checks for the method
	 *
	 *
	 * Used as a conditional before calling apply in some modules
	 *
	 * @access	public
	 */
	function check_method_exists($method)
	{
		$field_type = &$this->field_types[$this->field_type];
		
		if (method_exists($field_type, $method))
		{
			return TRUE;
		}
		
		return FALSE;
	}

	// --------------------------------------------------------------------
	
	/**
	 * Adds new custom field table fields
	 *
	 *
	 * Add new fields to channel_data on custom field creation
	 *
	 * @access	public
	 * @param	array	
	 * @return	void
	 */
	function add_datatype($field_id, $data)
	{
		$this->set_datatype($field_id, $data, array(), TRUE);
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Delete custom field table fields
	 *
	 *
	 * Deletes fields from channel_data on custom field deletion
	 *
	 * @access	public
	 * @param	array	
	 * @return	void
	 */
	function delete_datatype($field_id, $data)
	{
		// merge in a few variables to the data array
		$data['field_id'] = $field_id;
		$data['ee_action'] = 'delete';
		
		$fields = $this->apply('settings_modify_column', array($data));
		
		if ( ! isset($fields['field_id_'.$field_id]))
		{
			$fields['field_id_'.$field_id] = '';
		}
		
		if ( ! isset($fields['field_ft_'.$field_id]))
		{
			$fields['field_ft_'.$field_id] = '';
		}		

		$this->EE->load->dbforge();
		$delete_fields = array_keys($fields);
				
		foreach ($delete_fields as $col)
		{
			$this->EE->dbforge->drop_column('channel_data', $col);
		}
	}	
	
	// --------------------------------------------------------------------
	
	/**
	 * Edit custom field table fields
	 *
	 *
	 * Compares old field data to new field data and adds/modifies exp_channel_data
	 * fields as needed
	 *
	 * @access	public
	 * @param	mixed (field_id)
	 * @param	string (field_type)	
	 * @param	array	
	 * @return	void
	 */
	function edit_datatype($field_id, $field_type, $data)
	{
		$old_fields = array();
		
		// First we get the data
		$query = $this->EE->db->get_where('channel_fields', array('field_id' => $field_id));
		
		$this->setup_handler($query->row('field_type'));
		
		// Field type changed ?
		$type = ($query->row('field_type') == $field_type) ? 'get_data' : 'delete';

		$old_data = $query->row_array();
		
		// merge in a few variables to the data array
		$old_data['field_id'] = $field_id;
		$old_data['ee_action'] = $type;

		$old_fields = $this->apply('settings_modify_column', array($old_data));

		// Switch handler back to the new field type
		$this->setup_handler($field_type);

		if ( ! isset($old_fields['field_id_'.$field_id]))
		{
			$old_fields['field_id_'.$field_id]['type'] = 'text';
			$old_fields['field_id_'.$field_id]['null'] = TRUE;
		}
		
		if ( ! isset($old_fields['field_ft_'.$field_id]))
		{
			$old_fields['field_ft_'.$field_id]['type'] = 'tinytext';
			$old_fields['field_ft_'.$field_id]['null'] = TRUE;
		}

		// Delete extra fields
		if ($type == 'delete')
		{
			$this->EE->load->dbforge();
			$delete_fields = array_keys($old_fields);
				
			foreach ($delete_fields as $col)
			{
				if ($col == 'field_id_'.$field_id OR $col == 'field_ft_'.$field_id)
				{
					continue;
				}

				$this->EE->dbforge->drop_column('channel_data', $col);
			}
			
		}
		
		$type_change = ($type == 'delete') ? TRUE : FALSE;
		
		$this->set_datatype($field_id, $data, $old_fields, FALSE, $type_change);
	}	
	
	// --------------------------------------------------------------------
	
	/**
	 * Set data type
	 *
	 *
	 * Used primarily by add_datatype and edit_datatype to do the actual table manipulation
	 * when a custom field is added or edited
	 *
	 * @access	public
	 * @param	mixed (field_id)
	 * @param	array (new custom field data)	
	 * @param	array (old custom field data)		
	 * @param	bool (TRUE if it is a new field)
	 * @param	bool (TRUE if the field type changed)
	 * @return	void
	 */
	function set_datatype($field_id, $data, $old_fields = array(), $new = TRUE, $type_change = FALSE)
	{		
		$this->EE->load->dbforge();
		
		// merge in a few variables to the data array
		$data['field_id'] = $field_id;
		$data['ee_action'] = 'add';
		
		// We have to get the new fields regardless to check whether they were modified
		$fields = $this->apply('settings_modify_column', array($data));
		
		if ( ! isset($fields['field_id_'.$field_id]))
		{
			$fields['field_id_'.$field_id]['type'] = 'text';
			$fields['field_id_'.$field_id]['null'] = TRUE;
		}
		
		if ( ! isset($fields['field_ft_'.$field_id]))
		{
			$fields['field_ft_'.$field_id]['type'] = 'tinytext';
			$fields['field_ft_'.$field_id]['null'] = TRUE;
		}
		
		// Do we need to modify the field_id
		$modify = FALSE;

		if ( ! $new)
		{
			$diff1 = array_diff_assoc($old_fields['field_id_'.$field_id], $fields['field_id_'.$field_id]);
			$diff2 = array_diff_assoc($fields['field_id_'.$field_id], $old_fields['field_id_'.$field_id]);
		
			if ( ! empty($diff1) OR ! empty($diff2))
			{
				$modify = TRUE;
			}
		}

		// Add any new fields
		if ($type_change == TRUE OR $new == TRUE)
		{
			foreach ($fields as $field => $prefs)
			{
				if ( ! $new)
				{
					if ($field == 'field_id_'.$field_id OR $field == 'field_ft_'.$field_id)
					{
						continue;
					}
				}
				
				$this->EE->dbforge->add_column('channel_data', array($field => $prefs));
				
				// Make sure the value is an empty string
				$this->EE->db->update(
					'channel_data',
					array(
						$field => (isset($prefs['default'])) ? $prefs['default'] : ''
					)
				);
			}
		}
		
		// And modify any necessary fields
		if ($modify == TRUE)
		{
			$mod['field_id_'.$field_id] = $fields['field_id_'.$field_id];
			$mod['field_id_'.$field_id]['name'] = 'field_id_'.$field_id;
			
			$this->EE->dbforge->modify_column('channel_data', $mod);
		}
	}

	
	// --------------------------------------------------------------------
	
	/**
	 * Get custom field info from modules
	 *
	 * @access	public
	 */	
	function get_required_fields($channel_id)
	{
		$this->EE->load->model('channel_model');
		$required = array('title', 'entry_date');
		
		$query = $this->EE->channel_model->get_channel_info($channel_id, array('field_group'));
		
		if ($query->num_rows() > 0)
		{
			$row = $query->row();
			$fields = $this->EE->channel_model->get_required_fields($row->field_group);
		
			if ($fields->num_rows() > 0)
			{
				foreach ($fields->result() as $row)
				{
					$required[] = $row->field_id;
				}
			}
		}

		$module_data = $this->get_module_fields($channel_id);
	
		if ($module_data && is_array($module_data))
		{
			foreach ($module_data as $tab => $v)
			{
				foreach ($v as $val)
				{			
					if ($val['field_required'] == 'y')
					{
						$required[] =  $val['field_id'];
					}
				}
			}
		}
		
		return $required;

	}

	// --------------------------------------------------------------------
	
	/**
	 * Get custom field info from modules
	 *
	 * @access	public
	 */	
	function get_module_fields($channel_id, $entry_id = '')
	{
		$tab_modules = $this->get_modules();
		
		$set = FALSE;
		
		if ($tab_modules == FALSE)
		{
			return FALSE;
		}
		
		foreach ($tab_modules as $name)
		{
			$directory	= strtolower($name);
			$class_name	= ucfirst($directory).'_tab';
			
			$mod_base_path = $this->_include_tab_file($directory);

			$this->EE->load->add_package_path($mod_base_path, FALSE);

			$OBJ = new $class_name();

			if (method_exists($OBJ, 'publish_tabs') === TRUE)
			{
				// fetch the content
				$fields = $OBJ->publish_tabs($channel_id, $entry_id);

				// There's basically no way this *won't* be set, but let's check it anyhow.
				// When we find it, we'll append the module's classname to it to prevent
				// collission with other modules with similarly named fields. This namespacing
				// gets stripped as needed when the module data is processed in get_module_methods()
				// This function is called for insertion and editing of entries.
				// @php4 would be nice to use a reference in this foreach...
				
				foreach ($fields as $key => $field)
				{
					if (isset($field['field_id']))
					{
						$fields[$key]['field_id'] = $name.'__'.$field['field_id']; // two underscores
					}
				}

				$set[$name] = $fields;
			}

		// restore our package and view paths
		$this->EE->load->remove_package_path($mod_base_path);
		
		}
		
		return $set;
	}
	
	
	// --------------------------------------------------------------------
	
	/**
	 * Get custom field info from modules
	 *
	 * @access	public
	 */	
	function get_module_methods($methods, $params = array())
	{
		$tab_modules = $this->get_modules();
		
		$set = FALSE;
		
		if ($tab_modules == FALSE)
		{
			return FALSE;
		}
		
		if ( ! is_array($methods))
		{
			$methods = array($methods);
		}

		foreach ($tab_modules as $name)
		{
			$directory	= strtolower($name);
			$class_name	= ucfirst($directory).'_tab';
			
			$mod_base_path = $this->_include_tab_file($directory);

			$this->EE->load->add_package_path($mod_base_path, FALSE);

			$OBJ = new $class_name();

			foreach ($methods as $method)
			{
				// if this data is getting inserted into the database, then we need to ensure we've
				// removed the automagically added classname from the field names
				if (isset($params['publish_data_db']['mod_data']))
				{
					$params['publish_data_db']['mod_data'] = $this->_clean_module_names($params['publish_data_db']['mod_data'], array($name));
				}
				elseif (isset($params['validate_publish'][0]))
				{
					$params['validate_publish'][0] = $this->_clean_module_names($params['validate_publish'][0], array($name));
				}

				if (method_exists($OBJ, $method) === TRUE)
				{
					if ( ! isset($params[$method]))
					{
						$params[$method] = '';
					}

					// fetch the content
					if ($method == 'publish_tabs')
					{
						$channel_id = $params['publish_tabs'][0]; 
						$entry_id = $params['publish_tabs'][1]; 
												
						// fetch the content
						$fields = $OBJ->publish_tabs($channel_id, $entry_id);

						$set[$name]['publish_tabs'] = $fields;
					}
					else
					{
						$set[$name][$method] = $OBJ->$method($params[$method]);
					}
				}
			}
		
			// restore our package and view paths
			$this->EE->load->remove_package_path($mod_base_path);
		
		}

		return $set;
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Include Tab File
	 *
	 * Loads the tab if it hasn't been used and returns the base path that
	 * can then be used to add the correct package paths.
	 *
	 * @access	public
	 */
	function _include_tab_file($name)
	{
		static $paths = array();
				
		// Have we encountered this one before?
		if ( ! isset($paths[$name]))
		{
			$class_name = ucfirst($name).'_tab';
			
			// First or third party?
			foreach(array(APPPATH.'modules/', PATH_THIRD) as $tmp_path)
			{
				if (file_exists($tmp_path.$name.'/tab.'.$name.'.php'))
				{
					$paths[$name] = $tmp_path.$name.'/';
					break;
				}
			}
			
			// Include file
			if ( ! class_exists($class_name))
			{
				if ( ! isset($paths[$name]))
				{
					show_error(sprintf($this->EE->lang->line('unable_to_load_tab'), 'tab.'.$name.'.php'));
				}
				
				include_once($paths[$name].'tab.'.$name.'.php');
			}
		}
		
		return $paths[$name];
	}

	// --------------------------------------------------------------------
	
	/**
	 * Clean Module Names
	 *
	 * This function removes the automatically added module classname from its field inputs
	 *
	 * @access	public
	 */
	function _clean_module_names($array_to_clean = array(), $module_names = array())
	{
		if (empty($module_names))
		{
			return $array_to_clean;
		}

		if (isset($array_to_clean['revision_post']))
		{
			$array_to_clean['revision_post'] = $this->_clean_module_names($array_to_clean['revision_post'], $module_names);
		}

		foreach ($array_to_clean as $field => $value)
		{
			// loop through each module name
			foreach($module_names as $module_name)
			{
				$module_name.="__";
				
				if (strncmp($field, $module_name, strlen($module_name)) == 0)
				{
					// new name
					$cleared_field_name = str_replace($module_name, '', $field); // avoid passing the entire $module_names array for swapping to avoid common naming situations
				
					// unset old
					unset($array_to_clean[$field]);
					//reset new
					$array_to_clean[$cleared_field_name] = $value;
				}
			}
		}

		return $array_to_clean;
	}

	// --------------------------------------------------------------------

	function get_modules()
	{
		// Do we have modules in play
		$this->EE->load->model('addons_model');
		$custom_field_modules = FALSE;
		
		$mquery = $this->EE->addons_model->get_installed_modules(FALSE, TRUE);
			
		if ($mquery->num_rows() > 0)
		{
			foreach($mquery->result_array() as $row)
			{
				$custom_field_modules[] = $row['module_name'];
			}
		}
		
		return $custom_field_modules;
	}
	
	// --------------------------------------------------------------------

	function setup_entry_settings($channel_id, $entry_data, $bookmarklet = FALSE)
	{
		// Let's grab our channel data- note should be cached if already called via api
		$this->EE->api->instantiate('channel_structure');

		$channel_query = $this->EE->api_channel_structure->get_channel_info($channel_id);
		
		if ($channel_query->num_rows() == 0)
		{
			// bad return false?
		}
		
		$channel_data = $channel_query->row_array();
		
		$dst_enabled = ($this->EE->session->userdata('daylight_savings') == 'y' ? TRUE : FALSE);		
		
		// We start by setting our default fields
		
		$title = (isset($entry_data['title'])) ? $entry_data['title'] : '';
		
		if ($channel_data['default_entry_title'] != '' && $title == '')
		{
			$title = $channel_data['default_entry_title'];
		}
		
		
		$url_title = (isset($entry_data['url_title'])) ? $entry_data['url_title'] : '';
		

		$deft_fields = array(
			'title' 		=> array(
				'field_id'				=> 'title',
				'field_label'			=> lang('title'),
				'field_required'		=> 'y',
				'field_data'			=> $title,
				'field_show_fmt'		=> 'n',
				'field_instructions'	=> '',
				'field_text_direction'	=> 'ltr',
				'field_type'			=> 'text',
				'field_maxl'			=> 100
			),
			'url_title'		=> array(
				'field_id'				=> 'url_title',
				'field_label'			=> lang('url_title'),
				'field_required'		=> 'n',
				'field_data'			=> $url_title,
				'field_fmt'				=> 'xhtml',
				'field_instructions'	=> '',
				'field_show_fmt'		=> 'n',
				'field_text_direction'	=> 'ltr',
				'field_type'			=> 'text',
				'field_maxl'			=> 75
			),
			'entry_date'	=> array(
				'field_id'				=> 'entry_date',
				'field_label'			=> lang('entry_date'),
				'field_required'		=> 'y',
				'field_type'			=> 'date',
				'field_text_direction'	=> 'ltr',
				'field_data'			=> (isset($entry_data['entry_date'])) ? $entry_data['entry_date'] : '',
				'field_fmt'				=> 'text',
				'field_instructions'	=> '',
				'field_show_fmt'		=> 'n',
				'always_show_date'		=> 'y',
				'default_offset'		=> 0,
				'selected'				=> 'y',
				'dst_enabled'			=> $dst_enabled				
			),
			'expiration_date' => array(
				'field_id'				=> 'expiration_date',
				'field_label'			=> lang('expiration_date'),
				'field_required'		=> 'n',
				'field_type'			=> 'date',
				'field_text_direction'	=> 'ltr',
				'field_data'			=> (isset($entry_data['expiration_date'])) ? $entry_data['expiration_date'] : '',
				'field_fmt'				=> 'text',
				'field_instructions'	=> '',
				'field_show_fmt'		=> 'n',
				'default_offset'		=> 0,
				'selected'				=> 'y',
				'dst_enabled'			=> $dst_enabled				
			)	
		);

//wtf dst_enabled?		
		
		
		// comment expiry here.
		if (isset($this->EE->cp->installed_modules['comment']))
		{
			$deft_fields['comment_expiration_date'] = array(
				'field_id'				=> 'comment_expiration_date',
				'field_label'			=> lang('comment_expiration_date'),
				'field_required'		=> 'n',
				'field_type'			=> 'date',
				'field_text_direction'	=> 'ltr',
				'field_data'			=> (isset($entry_data['comment_expiration_date'])) ? $entry_data['comment_expiration_date'] : '',
				'field_fmt'				=> 'text',
				'field_instructions'	=> '',
				'field_show_fmt'		=> 'n',
				'default_offset'		=> $channel_data['comment_expiration'] * 86400,
				'selected'				=> 'y',
				'dst_enabled'			=> $dst_enabled
			);
		}
		
		foreach ($deft_fields as $field_name => $f_data)
		{
			$this->set_settings($field_name, $f_data);
		}
		

		// Now we set our custom fields
		
		// Get Channel fields in the field group
		$channel_fields = $this->EE->channel_model->get_channel_fields($channel_data['field_group']);

		

		$field_settings = array();

		foreach ($channel_fields->result_array() as $row)
		{
			$field_fmt 		= $row['field_fmt'];
			$field_dt 		= '';
			$field_data		= '';
			$dst_enabled	= '';
						
			if ($bookmarklet)
			{
				// Bookmarklet data perhaps?
				if (($field_data = $this->EE->input->get('field_id_'.$row['field_id'])) !== FALSE)
				{
					$field_data = $this->EE->functions->bm_qstr_decode($this->EE->input->get('tb_url')."\n\n".$field_data );
				}
			}
			else
			{
				$field_data = (isset($entry_data['field_id_'.$row['field_id']])) ? $entry_data['field_id_'.$row['field_id']] : $field_data;				
				$field_dt	= (isset($entry_data['field_dt_'.$row['field_id']])) ? $entry_data['field_dt_'.$row['field_id']] : 'y';
				$field_fmt	= (isset($entry_data['field_ft_'.$row['field_id']])) ? $entry_data['field_ft_'.$row['field_id']] : $field_fmt;				
			}			

			$settings = array(
				'field_instructions'	=> trim($row['field_instructions']),
				'field_text_direction'	=> ($row['field_text_direction'] == 'rtl') ? 'rtl' : 'ltr',
				'field_fmt'				=> $field_fmt,
				'field_dt'				=> $field_dt,
				'field_data'			=> $field_data,
				'field_name'			=> 'field_id_'.$row['field_id'],
				'dst_enabled'			=> $dst_enabled
			);
			
			$ft_settings = array();

			if (isset($row['field_settings']) && strlen($row['field_settings']))
			{
				$ft_settings = unserialize(base64_decode($row['field_settings']));
			}
			
			$settings = array_merge($row, $settings, $ft_settings);
			$this->EE->api_channel_fields->set_settings($row['field_id'], $settings);
			
			$field_settings[$settings['field_name']] = $settings;
		}
		
		// Merge the default and custom fields
		
		return array_merge($deft_fields, $field_settings);


	}
	
	/**
	 * update/add field
	 *
	 * omit field_id in $field_data to create a new field
	 * 
	 * @param array $field_data the field settings;
	 *                          uses the following keys: group_id, site_id, field_name, field_label, field_type, field_order,
	 *                          and also fieldtype-specific settings, e.g. text_field_text_direction.
	 *                          works in concert with data submitted using Api_channel_fields::field_edit_vars()
	 * 
	 * @return int|string|FALSE the field_id or FALSE if the process failed
	 */
	public function update_field(array $field_data)
	{
		$this->errors = array();
		
		$this->EE->load->helper('array');
		
		if ( ! isset($field_data['group_id']))
		{
			$this->_set_error('unauthorized_access');
			
			return FALSE;
		}
		
		$this->EE->lang->loadfile('admin_content');

		// If the $field_id variable has data we are editing an
		// existing group, otherwise we are creating a new one

		$edit = ( ! isset($field_data['field_id']) OR $field_data['field_id'] == '') ? FALSE : TRUE;

		// We need this as a variable as we'll unset the array index

		$group_id = element('group_id', $field_data);

		// Check for required fields

		$error = array();
		$this->EE->load->model('field_model');

		// little check in case they switched sites in MSM after leaving a window open.
		// otherwise the landing page will be extremely confusing
		if ( ! isset($field_data['site_id']) OR $field_data['site_id'] != $this->EE->config->item('site_id'))
		{
			$this->_set_error('site_id_mismatch');
		}

		// Was a field name supplied?
		if ($field_data['field_name'] == '')
		{
			$this->_set_error('no_field_name');
		}
		// Is the field one of the reserved words?
		else if (in_array($field_data['field_name'], $this->EE->cp->invalid_custom_field_names()))
		{
			$this->_set_error('reserved_word');
		}

		// Was a field label supplied?
		if ($field_data['field_label'] == '')
		{
			$this->_set_error('no_field_label');
		}

		// Does field name contain invalid characters?
		if (preg_match('/[^a-z0-9\_\-]/i', $field_data['field_name']))
		{
			$this->errors[] = lang('invalid_characters').': '.$field_data['field_name'];
		}

		// Is the field name taken?
		$this->EE->db->where(array(
			'site_id' => $this->EE->config->item('site_id'),
			'field_name' => element('field_name', $field_data),
		));

		if ($edit == TRUE)
		{
			$this->EE->db->where('field_id !=', element('field_id', $field_data));
		}

		if ($this->EE->db->count_all_results('channel_fields') > 0)
		{
			$this->_set_error('duplicate_field_name');
		}

		$field_type = $field_data['field_type'];

		// If they are setting a file type, ensure there is at least one upload directory available
		if ($field_type == 'file')
		{
			$this->EE->load->model('file_upload_preferences_model');
			$upload_dir_prefs = $this->EE->file_upload_preferences_model->get_file_upload_preferences();
			
			// count upload dirs
			if (count($upload_dir_prefs) === 0)
			{
				$this->EE->lang->loadfile('filemanager');
				$this->_set_error('please_add_upload');
			}
		}

		// Are there errors to display?

		if ($this->error_count() > 0)
		{
			return FALSE;
		}
		
		$native = array(
			'field_id', 'site_id', 'group_id',
			'field_name', 'field_label', 'field_instructions',
			'field_type', 'field_list_items', 'field_pre_populate',
			'field_pre_channel_id', 'field_pre_field_id',
			'field_related_id', 'field_related_orderby', 'field_related_sort', 'field_related_max',
			'field_ta_rows', 'field_maxl', 'field_required',
			'field_text_direction', 'field_search', 'field_is_hidden', 'field_fmt', 'field_show_fmt',
			'field_order'
		);
		
		$_posted = array();
		$_field_posted = preg_grep('/^'.$field_type.'_.*/', array_keys($field_data));
		$_keys = array_merge($native,  $_field_posted);

		foreach($_keys as $key)
		{
			if (isset($field_data[$key]))
			{
				$_posted[$key] = $field_data[$key];
			}
		}

		// Get the field type settings
		$this->fetch_all_fieldtypes();
		$this->setup_handler($field_type);
		$ft_settings = $this->apply('save_settings', array($_posted));
		
		// Default display options
		foreach(array('smileys', 'glossary', 'spellcheck', 'formatting_btns', 'file_selector', 'writemode') as $key)
		{
			$tmp = $this->_get_ft_data($field_type, 'field_show_'.$key, $field_data);
			$ft_settings['field_show_'.$key] = $tmp ? $tmp : 'n';
		}
		
		// Now that they've had a chance to mess with the POST array,
		// grab post values for the native fields (and check namespaced fields)
		foreach($native as $key)
		{
			$native_settings[$key] = $this->_get_ft_data($field_type, $key, $field_data);
		}
		
		// Set some defaults
		$native_settings['field_related_id']		= ($tmp = $this->_get_ft_data($field_type, 'field_related_channel_id', $field_data)) ? $tmp : '0';
		$native_settings['field_list_items']		= ($tmp = $this->_get_ft_data($field_type, 'field_list_items', $field_data)) ? $tmp : '';
				
		$native_settings['field_text_direction']	= ($native_settings['field_text_direction'] !== FALSE) ? $native_settings['field_text_direction'] : 'ltr';
		$native_settings['field_show_fmt']			= ($native_settings['field_show_fmt'] !== FALSE) ? $native_settings['field_show_fmt'] : 'n';
		$native_settings['field_fmt']				= ($native_settings['field_fmt'] !== FALSE) ? $native_settings['field_fmt'] : 'xhtml';
		
		if ($native_settings['field_list_items'] != '')
		{
			// This results in double encoding later on
			//$this->load->helper('string');
			//$native_settings['field_list_items'] = quotes_to_entities($native_settings['field_list_items']);
		}
		
		if ($native_settings['field_pre_populate'] == 'y')
		{
			$x = explode('_', $this->_get_ft_data($field_type, 'field_pre_populate_id', $field_data));

			$native_settings['field_pre_channel_id']	= $x['0'];
			$native_settings['field_pre_field_id'] = $x['1'];
		}
		
		// If they returned a native field value as part of their settings instead of changing the post array,
		// we'll merge those changes into our native settings
		
		foreach($ft_settings as $key => $val)
		{
			if (in_array($key, $native))
			{
				unset($ft_settings[$key]);
				$native_settings[$key] = $val;
			}
		}

		if ($field_data['field_order'] == 0 OR $field_data['field_order'] == '')
		{
			$query = $this->EE->db->select('MAX(field_order) as max')
					      ->where('site_id', $this->EE->config->item('site_id'))
					      ->where('group_id', (int) $group_id)
					      ->get('channel_fields');
				
			$native_settings['field_order'] = (int) $query->row('max') + 1;
		}
		
		$native_settings['field_settings'] = base64_encode(serialize($ft_settings));
		
		// Construct the query based on whether we are updating or inserting
		if ($edit === TRUE)
		{
			if ( ! is_numeric($native_settings['field_id']))
			{
				return FALSE;
			}

			// Update the formatting for all existing entries
			if ($this->_get_ft_data($field_type, 'update_formatting', $field_data) == 'y')
			{
				$this->EE->db->update(
					'channel_data',
					array('field_ft_'.$native_settings['field_id'] => $native_settings['field_fmt'])
				);
			}

				
			// Send it over to drop old fields, add new ones, and modify as needed
			$this->edit_datatype(
				$native_settings['field_id'],
				$field_type,
				$native_settings
			);
			
			unset($native_settings['group_id']);
			
			$this->EE->db->where('field_id', $native_settings['field_id']);
			$this->EE->db->where('group_id', $group_id);
			$this->EE->db->update('channel_fields', $native_settings);

			// Update saved layouts if necessary
			$collapse = ($native_settings['field_is_hidden'] == 'y') ? TRUE : FALSE;
			$buttons = ($ft_settings['field_show_formatting_btns'] == 'y') ? TRUE : FALSE;
			
			// Add to any custom layouts
			// First, figure out what channels are associated with this group
			// Then using the list of channels, figure out the layouts associated with those channels
			// Then update each layout individually
			
			$channels_for_group = $this->EE->field_model->get_assigned_channels($group_id);
			
			if ($channels_for_group->num_rows() > 0)
			{
				$this->EE->load->model('layout_model');
				
				foreach ($channels_for_group->result() as $channel)
				{
					$channel_ids[] = $channel->channel_id;
				}
				
				$this->EE->db->select('layout_id');
				$this->EE->db->where_in('channel_id', $channel_ids);
				$layouts_for_group = $this->EE->db->get('layout_publish');
				
				foreach ($layouts_for_group->result() as $layout) 
				{
					// Figure out visibility for the field in the layout
					$layout_settings = $this->EE->layout_model->get_layout_settings(array('layout_id' => $layout->layout_id), TRUE);
					
					$visibility = TRUE;
					$width = '100%';
					
					if (array_key_exists('field_id_'.$native_settings['field_id'], $layout_settings)) 
					{
						$field_settings = $layout_settings['field_id_'.$native_settings['field_id']];
						
						$width = ($field_settings['width'] !== NULL) ? 
							$field_settings['width'] : 
							$width;
						
						$visibility = ($field_settings['visible'] !== NULL) ? 
							$field_settings['visible'] : 
							$visibility;
					}
					
					$field_info[$native_settings['field_id']] = array(
						'visible'     => $visibility,
						'collapse'    => $collapse,
						'htmlbuttons' => $buttons,
						'width'       => $width
					);
					
					$this->EE->layout_model->edit_layout_group_fields($field_info, $layout->layout_id);
				}
			}
		}
		else
		{
			if ( ! $native_settings['field_ta_rows'])
			{
				$native_settings['field_ta_rows'] = 0;
			}

			// as its new, there will be no field id, unset it to prevent an empty string from attempting to pass
			unset($native_settings['field_id']);

			$this->EE->db->insert('channel_fields', $native_settings);

			$insert_id = $this->EE->db->insert_id();
			$native_settings['field_id'] = $insert_id;

			$this->add_datatype(
				$insert_id, 
				$native_settings
			);

			$this->EE->db->update('channel_data', array('field_ft_'.$insert_id => $native_settings['field_fmt'])); 

			$field_formatting = array('none', 'br', 'xhtml');
			
			//if the selected field formatting is not one of the native formats, make sure it gets added to exp_field_formatting for this field
			if ( ! in_array($native_settings['field_fmt'], $field_formatting))
			{
				$field_formatting[] = $native_settings['field_fmt'];
			}

			foreach ($field_formatting as $val)
			{
				$f_data = array('field_id' => $insert_id, 'field_fmt' => $val);
				$this->EE->db->insert('field_formatting', $f_data); 
			}
			
			$collapse = ($native_settings['field_is_hidden'] == 'y') ? TRUE : FALSE;
			$buttons = ($ft_settings['field_show_formatting_btns'] == 'y') ? TRUE : FALSE;
			
			$field_info['publish'][$insert_id] = array(
								'visible'		=> 'true',
								'collapse'		=> $collapse,
								'htmlbuttons'	=> $buttons,
								'width'			=> '100%'
			);
			
			// Add to any custom layouts
			$query = $this->EE->field_model->get_assigned_channels($group_id);
			
			if ($query->num_rows() > 0)
			{
				foreach ($query->result() as $row)
				{
					$channel_ids[] = $row->channel_id;
				}
				
				$this->EE->load->library('layout');
				$this->EE->layout->add_layout_fields($field_info, $channel_ids);
			}
		}
		
		$_final_settings = array_merge($native_settings, $ft_settings);
		unset($_final_settings['field_settings']);
		
		$this->set_settings($native_settings['field_id'], $_final_settings);
		$this->setup_handler($native_settings['field_id']);
		$this->apply('post_save_settings', array($_posted));

		$this->EE->functions->clear_caching('all', '', TRUE);
		
		return $native_settings['field_id'];
	}
	
	/**
	 * A utility to get fieldtype-specific settings from the $field_data array
	 * supplied by Api_channel_fields::update_field()
	 * 
	 * @param string $field_type the name of the field_type, e.g. text or select
	 * @param string $key the key of the setting to retrieve, e.g. field_text_direction
	 * @param array $field_data the full array of settings provided by the update_field() method
	 * 
	 * @return mixed the fieldtype setting requested
	 */
	protected function _get_ft_data($field_type, $key, $field_data)
	{
		if (isset($field_data[$key]))
		{
			return $field_data[$key];
		}
		
		$key = $field_type.'_'.$key;
		
		return (isset($field_data[$key])) ? $field_data[$key] : FALSE;
	}
	
	/**
	 * gets variables to be passed to the admin/field_edit view
	 * for new fields or existing fields
	 * 
	 * @param string|int $group_id
	 * @param string|int $field_id optional if new field
	 * @param array|false $field_types array of field types to present as field_type_options,
	 * 				   will show all valid field types if FALSE
	 * 
	 * @return array    the default fields needed to use the admin/field_edit view
	 */
	public function field_edit_vars($group_id, $field_id = FALSE, $field_types = FALSE)
	{
		$this->errors = array();
		
		$this->EE->load->library('table');

		$this->EE->load->model('field_model');
		
		$vars = array(
			'group_id' => $group_id,
			'field_id' => $field_id,
		);
		
		$this->EE->db->select('f.*');
		$this->EE->db->from('channel_fields AS f, field_groups AS g');
		$this->EE->db->where('f.group_id = g.group_id');
		$this->EE->db->where('g.site_id', $this->EE->config->item('site_id'));
		$this->EE->db->where('f.field_id', $vars['field_id']);
		
		$field_query = $this->EE->db->get();
		
		if ($field_id == '')
		{
			$type = 'new';

			foreach ($field_query->list_fields() as $f)
			{
				if ( ! isset($vars[$f]))
				{
					$vars[$f] = '';
				}
			}

			$this->EE->db->select('group_id');
			$this->EE->db->where('group_id', $vars['group_id']);
			$this->EE->db->where('site_id', $this->EE->config->item('site_id'));
			$query = $this->EE->db->get('channel_fields');

			$vars['field_order'] = $query->num_rows() + 1;

			if ($query->num_rows() > 0)
			{
				$vars['group_id'] = $query->row('group_id');
			}
			else
			{
				// if there are no existing fields yet for this group, this allows us to still validate the group_id
				$this->EE->db->where('group_id', $vars['group_id']);
				$this->EE->db->where('site_id', $this->EE->config->item('site_id'));

				if ($this->EE->db->count_all_results('field_groups') != 1)
				{
					$this->_set_error('unauthorized_access');
					
					return FALSE;
				}
			}
		}
		else
		{
			$type = 'edit';
			
			// No valid edit id?  No access
			if ($field_query->num_rows() == 0)
			{
				$this->_set_error('unauthorized_access');
				
				return FALSE;
			}

			foreach ($field_query->row_array() as $key => $val)
			{
				if ($key == 'field_settings' && $val)
				{
					$ft_settings = unserialize(base64_decode($val));
					$vars = array_merge($vars, $ft_settings);
				}
				else
				{
					$vars[$key] = $val;
				}
			}
			
			$vars['update_formatting']	= FALSE;
		}
		
		extract($vars);
		
		// Fetch the name of the group
		$query = $this->EE->field_model->get_field_group($group_id);
		
		$vars['group_name']			= $query->row('group_name');
		$vars['submit_lang_key']	= ($type == 'new') ? 'submit' : 'update';

		// Fetch the channel names

		$this->EE->db->select('channel_id, channel_title, field_group');
		$this->EE->db->where('site_id', $this->EE->config->item('site_id'));
		$this->EE->db->order_by('channel_title', 'asc');
		$query = $this->EE->db->get('channels');

		$vars['field_pre_populate_id_options'] = array();

		foreach ($query->result_array() as $row)
		{
			// Fetch the field names
			$this->EE->db->select('field_id, field_label');
			$this->EE->db->where('group_id', $row['field_group']);
			$this->EE->db->order_by('field_label','ASC');
			$rez = $this->EE->db->get('channel_fields');

			if ($rez->num_rows() > 0)
			{
				$vars['field_pre_populate_id_options'][$row['channel_title']] = array();
				
				foreach ($rez->result_array() as $frow)
				{
					$vars['field_pre_populate_id_options'][$row['channel_title']][$row['channel_id'].'_'.$frow['field_id']] = $frow['field_label'];
				}
			}
		}

		$vars['field_pre_populate_id_select'] = $field_pre_channel_id.'_'.$field_pre_field_id;

		// build list of formatting options
		if ($type == 'new')
		{
			$vars['edit_format_link'] = '';
			
			$this->EE->load->model('addons_model');
			
			$vars['field_fmt_options'] = $this->EE->addons_model->get_plugin_formatting(TRUE);
		}
		else
		{
			$confirm = "onclick=\"if( !confirm('".lang('list_edit_warning')."')) return false;\"";
			$vars['edit_format_link'] = '<strong><a '.$confirm.' href="'.BASE.AMP.'C=admin_content'.AMP.'M=edit_formatting_options'.AMP.'id='.$field_id.'" title="'.lang('edit_list').'">'.lang('edit_list').'</a></strong>';

			$this->EE->db->select('field_fmt');
			$this->EE->db->where('field_id', $field_id);
			$this->EE->db->order_by('field_fmt');
			$query = $this->EE->db->get('field_formatting');

			if ($query->num_rows() > 0)
			{
				foreach ($query->result_array() as $row)
				{
					$name = ucwords(str_replace('_', ' ', $row['field_fmt']));
				
					if ($name == 'Br')
					{
						$name = lang('auto_br');
					}
					elseif ($name == 'Xhtml')
					{
						$name = lang('xhtml');
					}
					$vars['field_fmt_options'][$row['field_fmt']] = $name;
				}
			}
		}

		$vars['field_fmt'] = (isset($field_fmt) && $field_fmt != '') ? $field_fmt : 'none';

		// Prep our own fields
		
		$fts = $this->fetch_installed_fieldtypes();
		
		$default_values = array(
			'field_type'					=> isset($fts['text']) ? 'text' : key($fts),
			'field_show_fmt'				=> 'n',
			'field_required'				=> 'n',
			'field_search'					=> 'n',
			'field_is_hidden'				=> 'n',
			'field_pre_populate'			=> 'n',
			'field_show_spellcheck'			=> 'n',
			'field_show_smileys'			=> 'n',
			'field_show_glossary'			=> 'n',
			'field_show_formatting_btns'	=> 'n',
			'field_show_writemode'			=> 'n',
			'field_show_file_selector'		=> 'n',
			'field_text_direction'			=> 'ltr'
		);

		foreach($default_values as $key => $val)
		{
			$vars[$key] = ( ! isset($vars[$key]) OR $vars[$key] == '') ? $val : $vars[$key];
		}
		
		foreach(array('field_pre_populate', 'field_required', 'field_search', 'field_show_fmt') as $key)
		{
			$current = ($vars[$key] == 'y') ? 'y' : 'n';
			$other = ($current == 'y') ? 'n' : 'y';
			
			$vars[$key.'_'.$current] = TRUE;
			$vars[$key.'_'.$other] = FALSE;
		}
		
		// Text Direction
		$current = $vars['field_text_direction'];
		$other = ($current == 'rtl') ? 'ltr' : 'rtl';
		
		$vars['field_text_direction_'.$current] = TRUE;
		$vars['field_text_direction_'.$other] = FALSE;
		
		// Grab Field Type Settings
		
		$vars['field_type_table']	= array();
		$vars['field_type_options']	= array();

		$created = FALSE;

		foreach($fts as $key => $attr)
		{
			if (is_array($field_types) && ! in_array($key, $field_types))
			{
				continue;
			}
			
			// Global settings
			$settings = unserialize(base64_decode($fts[$key]['settings']));
			
			$settings['field_type'] = $key;
			
			$this->EE->table->clear();
			
			$this->set_settings($key, $settings);
			$this->setup_handler($key);
			
			$str = $this->apply('display_settings', array($vars));

			$vars['field_type_tables'][$key]	= $str;
			$vars['field_type_options'][$key]	= $attr['name'];
			
			if (count($this->EE->table->rows))
			{
				$vars['field_type_tables'][$key] = $this->EE->table->rows;
			}
		}

		asort($vars['field_type_options']);	// sort by title

		$vars['form_hidden'] = array(
			'group_id'		=> $group_id,
			'field_id'		=> $field_id,
			'site_id'		=> $this->EE->config->item('site_id')
		);

		$vars['ft_selector'] = "#ft_".implode(", #ft_", array_keys($fts));
		
		return $vars;
	}
}

// END Api_channel_fields class

/* End of file Api_channel_fields.php */
/* Location: ./system/expressionengine/libraries/api/Api_channel_fields.php */